/**
 * @file   File.java
 * @author Enrique Madrid <e.madrid@lpcsite.com>
 * @date   Tue Jul 24 06:09:06 2007
 *
 *   Archivos de acceso aleatorio.
 *
 */

package sim.framework.io;

import java.io.IOException;
import javax.microedition.rms.*;
import java.io.*;

import sim.framework.*;
import sim.utils.Utils;

/**
 *  Archivos de acceso aleatorio.
 *
 * Esta clase permite la manipulación de archivos de bajo nivel para
 * acceso aleatorio.
 *
 * @author Enrique J. Madrid M.
 * @version %I%, %I%
 * @since 1.0
 */
public class File extends Io {
    
    // Modo de operación del archivo
    private static final byte MODE_NONE = 0;
    public static final byte MODE_READ = 1;
    public static final byte MODE_WRITE = 2;
    public static final byte MODE_APPEND = 3;
    public static final byte MODE_READEX = 4;
    public static final byte MODE_WRITEEX = 5;
    public static final byte MODE_APPENDEX = 6;
    
    // Referencia de posición puntero.
    public static final byte SEEK_CURRENT = 1;
    public static final byte SEEK_START = 2;
    public static final byte SEEK_END = 3;
    
    // Longitud máxima de record
    protected static final int RECORD_MAXLEN = 128;
    
    // Modos de encoding del file origen
    public static final byte TYPE_BIN = 0;
    public static final byte TYPE_TXT = 1;
    
    // Store.
    private RecordStore m_Store;
    private int m_StoreOpened;
    private int m_NumRecords;
    
    // Header
    private byte[] m_Header = new byte[File.RECORD_MAXLEN];
    private int m_Filesize;
    
    // Record
    private byte[] m_Record = new byte[File.RECORD_MAXLEN];
    private int m_RecordID;
    private int m_RecordIndex;
    private int m_RecordEnd;
    
    // File properties
    private String m_Filename;
    private byte m_Mode;
    protected int m_Offset;
    
    /**
     *  Construye un objeto que permite el acceso a un archivo.
     *
     * @param s Referencia a un objeto del tipo Simlet.
     *
     * @return Devuelve una referencia a un objeto del tipo File.
     */
    public File(Simlet s) {
        m_Simlet = s;
    }
    
    /**
     *  Abre un archivo en el modo especificado.
     *
     * @param s Referencia a un objeto del tipo Simlet.
     * @param filename Nombre del archivo.
     * @param mode Modo apertura. Ver File.MODE_XXX
     *
     * @return Devuelve una referencia a un objeto del tipo File
     *         apuntado por una referencia de la clase Io.
     */
    public static Io create(Simlet s, final String filename, byte mode) {
        File newFile;
        newFile = new File(s);
        if (newFile.open(filename, mode) == false) {
            newFile = null;
        }
        return newFile;
    }
    
    /**
     *  Abre un archivo en modo File.MODE_WRITEEX.
     *
     * @param s Referencia a un objeto del tipo Simlet.
     * @param filename Nombre del archivo.
     *
     * @return Devuelve una referencia a un objeto del tipo File
     *         apuntado por una referencia de la clase Io.
     */
    public static Io create(Simlet s, final String filename) {
        return File.create(s, filename, File.MODE_WRITEEX);
    }
    
    
    /**
     * Copia un archivo del paquete JAR al sistema de almacenamiento del terminal.
     *
     * Esta función es util para crear archivos utilizados por los distintos módulos.
     *
     * @param fsource Nombre del archivo almacenado en el paquete JAR.
     * @param ftarget Nombre del archivo en el sistema de almacenamiento del terminal.
     * @param srctype Tipo de archivo contenido en el paquete JAR. Este parámetro es
     *                util para especificar el modo en que se copiará el archivo. Si
     *                vale File::TYPE_BIN, el archivo será copiado tal cual se está
     *                en el paquete JAR; si vale File::TYPE_TXT, la fución utiliza
     *                la clase String para convertir el archivo de texto codificado
     *                UTF-8 (Utilizado por Soluciones Info Móviles) a la codifiación
     *                local del terminal.
     *
     * @return Si el archivo es creado satisfactoriamente devuelve true, en caso
     *         contrario devuelve false.
     */
    public static boolean create(Simlet s, final String fsource,
				 final String ftarget, final int strtype ) {
        boolean     bRes = false;
        InputStream is = null;
        File        file = null;
        byte[]      abyte = null;
        String      str;
        int         len;
        
        try {
            abyte = new byte[File.RECORD_MAXLEN];
            // Si abrimos el  archivo del paquete JAR ...
            is = s.getClass().getResourceAsStream( fsource );
            if( is != null ) {
                // ... y abrimos un archivo en el terminal ...
                file = (File)File.create( s, ftarget );
                if( file != null ) {
                    // ... copiamos de un archivo a otro.
                    while( (len = is.read(abyte)) != -1 ) {
                        if( strtype == File.TYPE_BIN ) {
                            file.write( abyte, 0, len );
                        } else {    // File.TYPE_TXT
                            str = new String( abyte, 0, len, "UTF-8" );
                            file.write( str.getBytes() );
                        }
                    }
                    // Cerrarmos el archivo del terminal.
                    file.close();
                    file = null;
                    bRes = true;
                }
                // Cerrarramos el archivo del paquete JAR.
                is.close();
                is = null;
            }
        } catch( IOException _ex ) {
            s.execFail(s.getLabel(Resource.LBL_FAIL_STORE_IOERROR) + fsource);
        } finally {
            if( is != null ) {
                try {
                    is.close();
                } catch (IOException ex) {
                }
            }
            if( file != null ) {
                file.close();
            }
        }
        
        return bRes;
    }

    public boolean opened() {
	return (m_Store != null);
    }
    
    /**
     *  Comprueba si un archivo existe.
     *
     * @param filename Nombre del archivo que se desea comprobar.
     *
     * @return Si el archivo existe devuelve true, en caso contrario devuelve false.
     */
    public static final boolean exists(final String filename) {
        String [] files;
        int i;
        files = RecordStore.listRecordStores();
        if (files != null) {
            for (i = 0; i < files.length ; i++) {
                if (filename.compareTo(files[i]) == 0) {
                    return true;
                }
            }
        }
        return false;
    }
    
    /**
     *  Posicion actual del puntero en el archivo.
     *
     * @return Devuelve la posición del puntero en el archivo.
     */
    public final int getOffset() {
        return m_Offset;
    }
    
    
    /**
     * Esta función se encarga de abrir el RecordStore que alamacena
     * la información de un archivo.  Si el archivo se crea por
     * primera vez, se crea su cabecera.
     */
    
    private boolean openStore(String filename, boolean create) {
        boolean ret;
        boolean fileExist;
        
        //#ifdef DEBUG_FILE
        //# 	System.out.println("File.openStore() ENTERED");
        //#endif

        ret = false;
        m_Filename = null;        
        try {
            // Intentar abrir el Record Store
            fileExist = File.exists(filename);
            m_Store = RecordStore.openRecordStore(filename, create );
            // Record Store abierto
            m_NumRecords = m_Store.getNumRecords();
            m_Filename = filename;
            m_StoreOpened++;
            // Si el archivo existia ...
            if (fileExist) {
                // ... leer su cabecera
                loadHeader();
            } else {
                // ... si no existe, crear su cabecera.
                saveHeader(true);
            }
            ret = true;
        } catch (RecordStoreNotFoundException rnfx) {
            if (create) {
                m_Simlet.execFail(m_Simlet.getLabel(Resource.LBL_FAIL_STORE_NOTFOUND) + filename);
            }
        } catch (RecordStoreFullException rfex) {
            m_Simlet.execFail(m_Simlet.getLabel(Resource.LBL_FAIL_STORE_FULLDISK) + filename);
        } catch (RecordStoreException rex) {
            m_Simlet.execFail(m_Simlet.getLabel(Resource.LBL_FAIL_STORE) + filename);
        } catch (IOException iox) {
            m_Simlet.execFail(m_Simlet.getLabel(Resource.LBL_FAIL_STORE_IOERROR) + filename);
        } finally {
            if (!ret) {
                lockedForClose();
            }
        }
        
        //#ifdef DEBUG_FILE
        //# 	System.out.println("File.openStore() EXITING");
        //#endif
        
        return ret;
    }
    
    private RecordStore getStore() {
        return m_Store;
    }
    
    /**
     * Header
     *
     */
    private void saveHeader(boolean create) throws RecordStoreFullException, InvalidRecordIDException, RecordStoreException, IOException {
        ByteArrayOutputStream byteOutputStream;
        DataOutputStream dataOutputStream;
        
        byteOutputStream = null;
        dataOutputStream = null;
        
        //#ifdef DEBUG_FILE
        //# 	System.out.println("File.saveHeader() ENTERED");
        //#endif
        
        try {
            // Streams de ayuda
            byteOutputStream = new ByteArrayOutputStream();
            dataOutputStream = new DataOutputStream(byteOutputStream);
            // Imprimir informacion de la cabecera
            dataOutputStream.writeInt(m_Filesize);
            //	    dataOutputStream.writeByte(m_Mode);
            dataOutputStream.flush();
            // Preparar cabecera
            Utils.memset(m_Header, (byte)0);
            Utils.memcpy(m_Header, 0,
			 byteOutputStream.toByteArray(), 0,
			 byteOutputStream.size());
            // Si el File es nuevo ...
            if (create) {
                // ... Crear la cabecera
                getStore().addRecord(m_Header, 0, File.RECORD_MAXLEN);
                m_NumRecords++;
            } else {
                // .. en caso contrario actualizar cabecera
                getStore().setRecord(1, m_Header, 0, File.RECORD_MAXLEN);
            }
        } finally {
            // ¿ Se ejecuta este finally si tiene que renviar una Exception!!!?
            try {
                if (dataOutputStream != null) {
                    dataOutputStream.close();
                    dataOutputStream = null;
                }
                if (byteOutputStream != null) {
                    byteOutputStream.close();
                    byteOutputStream = null;
                }
            } catch (IOException iox) {
		m_Simlet.execFail(m_Simlet.getLabel(Resource.LBL_FAIL_STORE_IOERROR) + m_Filename);
            }
        }
        
        //#ifdef DEBUG_FILE
        //# 	System.out.println("File.saveHeader() EXITING");
        //#endif
        
    }
    
    private void loadHeader() throws InvalidRecordIDException, RecordStoreException, IOException {
        ByteArrayInputStream byteInputStream;
        DataInputStream dataInputStream;
        
        byteInputStream = null;
        dataInputStream = null;
        
        //#ifdef DEBUG_FILE
        //# 	System.out.println("File.loadHeader() ENTERED");
        //#endif
        
        try {
            m_Store.getRecord(1, m_Header, 0);
            byteInputStream = new ByteArrayInputStream( m_Header );
            dataInputStream = new DataInputStream( byteInputStream );
            m_Filesize = dataInputStream.readInt();
            //m_Mode = dataInputStream.readBoolean();
        } finally {
            try {
                if (dataInputStream != null) {
                    dataInputStream.close();
                    dataInputStream = null;
                }
                if (byteInputStream != null) {
                    byteInputStream.close();
                    byteInputStream = null;
                }
            } catch (IOException iox) {
		m_Simlet.execFail(m_Simlet.getLabel(Resource.LBL_FAIL_STORE_IOERROR) + m_Filename);
            }
        }
        
        //#ifdef DEBUG_FILE
        //# 	System.out.println("File.loadHeader() EXITING");
        //#endif
    }
    
    /**
     *  Nombre del archivo.
     *
     *
     * @return Devuelve el nombre del archivo.
     */
    public final String getFilename() {
        return m_Filename;
    }
    
    /**
     *  Longitud, en bytes, de contenido del archivo.
     *
     *
     * @return Devuelve la longitud, en bytes, de los datos del archivo.
     */
    public final int getFilesize() {
        return m_Filesize;
    }
    
    /**
     * Record
     *
     */
    private boolean seekRecord() throws InvalidRecordIDException, RecordStoreException, IOException {
        boolean ret;
        int recID;
        ret = true;
        
        //#ifdef DEBUG_FILE
        //# 	System.out.println("File.seekRecord() ENTERED");
        //#endif
        
        // Calcular la posición del record que vamos a utilizar (m_Record).
        // Considerar 1 para la cabecera, y 1 porque el Indice de los Records en un store se basa en 1.
        recID = 1 + ( getOffset() / File.RECORD_MAXLEN ) + 1;
        // Si busca un record diferente al actual
        if (recID != m_RecordID) {
            // Cargar informacion del record.
            if (!loadRecord(recID)) {
                return false;
            }
            m_RecordID = recID;
        }
        // Calcular la posición dentro del record que vamos a utilizar
        m_RecordIndex = getOffset() % File.RECORD_MAXLEN;
        m_RecordEnd = (m_RecordID < m_NumRecords) ? File.RECORD_MAXLEN : getFilesize() % File.RECORD_MAXLEN;
        
        //#ifdef DEBUG_FILE
        //# 	System.out.println("\tUtilizando record: " + m_RecordID);
        //# 	System.out.println("\tUtilizando index : " + m_RecordIndex);
        //# 	System.out.println("\tUtilizando end   : " + m_RecordEnd);
        //#endif
        
        //#ifdef DEBUG_FILE
        //# 	System.out.println("File.seekRecord() EXITING");
        //#endif
        
        return ret;
    }
    
    private boolean loadRecord(int recID) throws InvalidRecordIDException, RecordStoreException, IOException {
        // Si se pide un record mayor que numero de records ...
        if (recID > m_NumRecords) {
            // ... es porque lo necesitaran para escritura.
            Utils.memset(m_Record, (byte)0);
        } else {
            // ... en caso contrario, cargar un record del Store.
            if (getStore().getRecord(recID, m_Record, 0) != File.RECORD_MAXLEN) {
                // Esto es muy grave
                return false;
            }
        }
        return true;
    }
    
    private boolean saveRecord() throws InvalidRecordIDException, RecordStoreException, IOException {
        
        if (m_RecordID > m_NumRecords) {
            // Añadir nuevo record.
            m_RecordID = getStore().addRecord(m_Record, 0, File.RECORD_MAXLEN);
            // Aumenta el número de records
            m_NumRecords++;
        } else {
            // Actulizar el record
            getStore().setRecord(m_RecordID, m_Record, 0, File.RECORD_MAXLEN);
        }
        
        //#ifdef DEBUG_FILE
        //# 	String msg;
        //# 	msg = "    Utilizando record ";
        //# 	msg += m_RecordID;
        //# 	msg += " (el primero es de cabecera) , para offset " + m_Offset;
        //# 	System.out.println(msg);
        //# 	System.out.println(new String(m_Record));
        //#endif
        
        return true;
    }
    
    
    /**
     * Multithread safety
     *
     */
    protected synchronized int lock(byte operation, final String filename, byte mode,
				    byte[] buffer, int start, int count) {
	int ret = 0;
        switch (operation) {
	case File.OPER_OPEN:
	    ret = lockedFor(operation, filename, mode, buffer, start, count);
	    break;
	default:
	    // Si el store esta abierto ...
	    if (opened()) {
		// enviamos el mensaje!
		ret = lockedFor(operation, filename, mode, buffer, start, count);
	    }
	}
	return ret;
    }
    
    protected int lockedFor(byte operation, final String filename, byte mode,
			    byte[] buffer, int start, int count) {
        int ret;
        ret = File.ERR;
        
        switch (operation) {
	case File.OPER_OPEN:
	    ret = lockedForOpen(filename, mode);
	    break;
	case File.OPER_CLOSE:
	    ret = lockedForClose();
	    break;
	case File.OPER_TRUNCATE:
	    ret = lockedForTruncate(count);
	    break;
	case File.OPER_WRITE:
	    ret = lockedForWrite(buffer, start, count);
	    break;
	case File.OPER_READ:
	    ret = lockedForRead(buffer, start, count);
	    break;
	case File.OPER_SEEK:
	    ret = lockedForSeek(mode, count);
	    break;
        }
        
        return ret;
    }
    
    protected int lockedForOpen(String filename, byte mode) {
        int ret;
        
        ret = File.ERR;
        
        //#ifdef DEBUG_FILE
        //# 	System.out.println("File.lockedForOpen() ENTERED");
        //#endif
        
        if (lockedForClose() == 0) {
            // Modo de operación
            switch(mode) {
	    case File.MODE_READ:
	    case File.MODE_READEX:
		// Abrir el archivo si existe.
		if (openStore(filename, false)) {
		    ret = 0;
		}
		break;
	    case File.MODE_WRITE:
		// Si abre o creamos un nuevo archivo ...
		if (openStore(filename, true)) {
		    // ... truncamos el archivo a 0 (cero).
		    ret = lockedForTruncate(0);
		}
		break;
	    case File.MODE_WRITEEX:
		// Si abre o crea un nuevo archivo ...
		if (openStore(filename, true)) {
		    // ... nos quedamos al principio del archivo.
		    ret = 0;
		}
		break;
	    case File.MODE_APPEND:
	    case File.MODE_APPENDEX:
		// Si abre o crea un nuevo archivo ...
		if (openStore(filename, true)) {
		    // ... nos ubicanos al final del archivo.
		    ret = lockedForSeek(File.SEEK_END, 0);
		}
		break;
            }
            
            // Si el archivo abrió...
            if (ret == 0) {
                // ... recordar el modo
                m_Mode = mode;
            }
            
        }
        
        //#ifdef DEBUG_FILE
        //# 	System.out.println("File.lockedForOpen() EXITING");
        //#endif
        
        return ret;
    }
    
    protected int lockedForClose() {
        int ret;
        ret = 0;
        
        //#ifdef DEBUG_FILE
        //# 	System.out.println("File.lockedForClose() ENTERED");
        //#endif
        
        if (opened()) {
            // Close until the RecordStoreNotOpenException is caught. The
            // store should only be opened once, but just to make sure.
            try {
                while ( true ) {
                    getStore().closeRecordStore();
                    m_StoreOpened--;
                }
            } catch ( RecordStoreNotOpenException rse ) {
                m_Store = null;
            } catch ( RecordStoreException e ) {
                ret = File.ERR;
            }
        }
        
        //#ifdef DEBUG_FILE
        //#  	System.out.println("File.lockedForClose() EXITING");
        //#endif
        
        return ret;
    }
    
    protected int lockedForTruncate(int count) {
        int ret;
        
        //#ifdef DEBUG_FILE
        //# 	System.out.println("File.lockedForTruncate() ENTERED");
        //#endif
        
        // Si el parametro no es correcto devuelve File.ERR
        if ((count < 0) || (count > m_Filesize)) {
            return File.ERR;
        }
        
        // Truncar solo si count es diferente de la longitud
        // actual.
        ret = 0;
        if (count != m_Filesize) {
            try {
                m_Filesize = count;
                m_Offset = 0;
                saveHeader(false);
            } catch (RecordStoreException rsx) {
		m_Err = true;
		m_Simlet.execFail(m_Simlet.getLabel(Resource.LBL_FAIL_STORE) + m_Filename);
            } catch (IOException iox) {
		m_Err = true;
		m_Simlet.execFail(m_Simlet.getLabel(Resource.LBL_FAIL_STORE_IOERROR) + m_Filename);
            }
	    finally {
		if (m_Err) {
		    ret = File.ERR;
		    lockedForClose();
		}
	    }
        }
        
        //#ifdef DEBUG_FILE
        //# 	System.out.println("File.lockedForTruncate() EXITING");
        //#endif
        
        return ret;
    }
    
    protected int lockedForWrite(final byte[] bufIn, int start, int count) {
        int maxWrite;		// Max. longitud de bytes a escribir.
        int ret;		// Bytes escritos.
        int maxCopy;		// Max. longitud de bytes a copiar en un Record.
        int filesize;		// Longitud actual del archivo.
        
        //#ifdef DEBUG_FILE
        //# 	System.out.println("File.lockedForWrite() ENTERED");
        //#endif
        
        ret = 0;
        try {
            // Escribir datos en disco
            maxWrite = Math.min(count, bufIn.length - start);
            ret = 0;
            filesize = m_Filesize;
            while (ret < maxWrite) {
                // Ubicar el record
                if (!seekRecord()) {
                    break;
                }
                // Copiar Record con datos de BufIn.
                maxCopy = Math.min(File.RECORD_MAXLEN - m_RecordIndex, maxWrite - ret);
                Utils.memcpy(m_Record, m_RecordIndex, bufIn, start + ret, maxCopy);
                // Grabar record (Operación de salida).
                if (!saveRecord()) {
                    break;
                }
                // Mover el offset e increntar el valor a retornar.
                m_Offset += maxCopy;
                ret += maxCopy;
                // Longitud del archivo
                if (m_Offset > filesize) {
                    filesize = m_Offset;
                }
            }
            // Si la longitud del archivo ha cambiado y no hay excepciones
            // guardar la cabecera. Dont worry be hacker!!!. :-)
            if (filesize != m_Filesize) {
                m_Filesize = filesize;
                saveHeader(false);
            }
        } catch (RecordStoreFullException rsfx) {
            m_Err = true;
            m_Simlet.execFail(m_Simlet.getLabel(Resource.LBL_FAIL_STORE_FULLDISK) + m_Filename);
        } catch (RecordStoreException rsx) {
            m_Err = true;
            m_Simlet.execFail(m_Simlet.getLabel(Resource.LBL_FAIL_STORE) + m_Filename);
        } catch (IOException iox) {
            m_Err = true;
	    m_Simlet.execFail(m_Simlet.getLabel(Resource.LBL_FAIL_STORE_IOERROR) + m_Filename);
        }
	finally {
	    if (m_Err) {
		lockedForClose();
		ret = File.ERR;
	    }
	}
        
        //#ifdef DEBUG_FILE
        //#  	System.out.println("File.lockedForWrite() EXITING");
        //#endif
        
        return ret;
    }
    
    protected int lockedForRead(byte[] bufOut, int start, int count) {
        int maxRead;		// Max. longitud de bytes a leer.
        int ret;		// Bytes leidos.
        int maxCopy;		// Max. longitud de bytes a copiar de un Record.
        
        //#ifdef DEBUG_FILE
        //#  	System.out.println("File.lockedForRead() ENTERED");
        //#endif

	ret = 0;        
        try {
            // Leer datos de disco
            maxRead = Math.min(count, bufOut.length - start);
            while (ret < maxRead) {
                // Si no logramos ubicar el record correspodiente a getOffset() ...
                if ((m_Offset >= getFilesize()) || !seekRecord()) {
                    // ... y no se ha leido datos ...
                    if (ret == 0) {
                        // ... entonces es fin de archivo
                        m_Eof = true;
                    }
                    break;
                }
                // Si ubicamos un Record, copiarlo en bufOut.
                maxCopy = Math.min(m_RecordEnd - m_RecordIndex, maxRead - ret);
                Utils.memcpy(bufOut, start + ret, m_Record, m_RecordIndex, maxCopy);
                // Mover el offset e increntar el valor a retornar.
                m_Offset += maxCopy;
                ret += maxCopy;
            }
        } catch (RecordStoreException rsx) {
            m_Err = true;
	    m_Simlet.execFail(m_Simlet.getLabel(Resource.LBL_FAIL_STORE) + m_Filename);
        } catch (IOException iox) {
            m_Err = true;
	    m_Simlet.execFail(m_Simlet.getLabel(Resource.LBL_FAIL_STORE_IOERROR) + m_Filename);
        }
	finally {
	    if (m_Err) {
		ret = File.ERR;
		lockedForClose();
	    }
	}
        
        //#ifdef DEBUG_FILE
        //#  	System.out.println("File.lockedForRead() EXITING");
        //#endif
        
        return ret;
    }
    
    protected int lockedForSeek(byte from, int count) {
        int offset;
        
        //#ifdef DEBUG_FILE
        //#  	System.out.println("File.lockedForSeek() ENTERED");
        //#endif
        
        // Calcular nueva posición
        offset = 0;
        switch (from) {
	case File.SEEK_CURRENT:
	    offset = m_Offset;
	case File.SEEK_START:
	    offset += count;
	    break;
	case File.SEEK_END:
	    offset = getFilesize() - count;
	    break;
        }
        
        // Si la nueva posción no es es válida ...
        if ((offset < 0) || (offset >  getFilesize())) {
            // ... retornar error
            return File.ERR;
        }
        
        // guardar nueva posición.
        m_Offset = offset;
        m_Eof = false;
        
        //#ifdef DEBUG_FILE
        //#  	System.out.println("File.lockedForSeek() EXITING");
        //#endif
        
        return 0;
    }
    
    /**
     *  Abre un archivo
     *
     * @param filename Nombre del archivo que se desea abrir.
     * @param mode Modo de operación con el archivo.
     *
     * "MODE_READ"
     *    Abre el archivo para lectura, si el archivo no existe, la función
     *    devuelve false.
     *
     * "MODE_WRITE"
     *    Abre el archivo para lectura y escritura, si el archivo no existe
     *    lo crea; si existe, lo abre y lo trunca a cero. Caso contario, la
     *    función devuelve false.
     *
     * "MODE_APPEND"
     *    Abre el archivo para lectura y escritura, si el archivo no existe
     *    lo crea; posiciona el puntero al final del archivo en ambos casos
     *    Si la función falla, devuelve false.
     *
     * "MODE_READEX"
     *    Igual que "r", por ahora ;-)
     *
     * "MODE_WRITEEX"
     *    Abre el archivo para lectura y escritura, si el archivo no existe
     *    lo crea; si existe, lo abre y el puntero mantiene su posición cero
     *    Si la función falla devuelve false.
     *
     * "MODE_APPENDEX"
     *    Igual que "a", por ahora ;-)
     *
     * @return Si tiene éxito devuelve true, en caso contrario devuelve false.
     */
    public boolean open(final String filename, final byte mode) {
        return (lock(File.OPER_OPEN, filename, mode, null, 0, 0) == 0);
    }
    
    /**
     *  Cierra el archivo.
     *
     * @return Si la función tiene éxito devuelve true, en caso contrario devuelve false.
     */
    public boolean close() {
        return (lock(File.OPER_CLOSE, null, (byte)0, null, 0, 0) == 0);
    }
    
    /**
     *  Reubica la posición del puntero.
     *
     * @param from Posición a partir de la cual se inicia el desplazamiento.
     * @param count Bytes que se debe desplazar.
     *
     * @return Si la función tiene éxito devuelve true, en caso contario devuelve false.
     */
    public boolean seek(byte from, int count) {
        return (lock(File.OPER_SEEK, null, from, null, 0, count) == 0);
    }
    
    /**
     *  Trunca la longitud del archivo a count bytes.
     *
     * @param count Nueva longitud del archivo, en bytes.
     *
     * @return Si tiene éxito devuelve true, en caso contrario devuelve false.
     */
    public boolean truncate(int count) {
        return (lock(File.OPER_TRUNCATE, null, (byte)0, null, 0, count) == 0);
    }
    
    /**
     *  Escribe a lo más MIN(count, bufIn.length - start) bytes.
     *
     * @param bufOut Arreglo de bytes, es el origen de los datos escritos.
     * @param start Posición del primer byte a escribir. Este valor debe
     *              estart entre 0 y bufIn.length.
     * @param count Cantidad de bytes que se desea escribir.
     *
     * @return Devuelve el número de bytes escritos. Este valor puede ser
     *         menor que MIN(count, bufIn.length - start), Por ejemplo,
     *         si no hay más bytes disponibles inmediatamente. En caso de
     *         error devuelve File.ERR.
     */
    public int write(final byte[] bufIn, int start, int count) {
        return lock(File.OPER_WRITE, null, (byte)0, bufIn, start, count);
    }
    
    /**
     *  Escribe a lo más (bufIn.length - start) bytes.
     *
     * @param bufOut Arreglo de bytes, es el origen de los datos escritos.
     * @param start Posición del primer byte a escribir. Este valor debe
     *              estart entre 0 y bufIn.length.
     *
     * @return Devuelve el número de bytes escritos. Este valor puede ser
     *         menor que count, (bufIn.length - start), Por ejemplo, si
     *         no hay más bytes disponibles inmediatamente. En caso de
     *         error devuelve File.ERR.
     */
    public int write(final byte[] bufIn, int start) {
        return write(bufIn, start, bufIn.length - start);
    }
    
    /**
     *  Escribe a lo más bufIn.length bytes.
     *
     * @param bufOut Arreglo de bytes, es el origen de los datos escritos.
     *
     * @return Devuelve el número de bytes escritos. Este valor puede ser
     *         menor que count, bufIn.length, Por ejemplo, si no hay más
     *         bytes disponibles inmediatamente. En caso de error devuelve
     *         File.ERR.
     */
    public int write(final byte[] bufIn) {
        return write(bufIn, 0, bufIn.length);
    }
    
    /**
     *  Lee a lo más MIN(count, bufOut.length - start) bytes.
     *
     * Los bytes son colocados en bufOut empesando de start.
     *
     * @param bufOut Arreglo de bytes, es el destino de los datos leídos.
     * @param start Posición del primer byte leído. Este valor debe estar
     *              entre 0 y bufOut.length.
     * @param count Cantidad de bytes que se desean leer.
     *
     * @return Devuelve el número de bytes leidos. Este valor puede ser
     *         menor que MIN(count, bufOut.length - start). Por ejemplo,
     *         si no hay más bytes en el archivo o si no hay más bytes
     *         disponibles inmediatamente. Si se llega al final del
     *         archivo devuelve 0 y eof() devuelve true. En caso de
     *         error devuelve File.ERR, y err() devuelve true.
     */
    public int read(byte[] bufOut, int start, int count) {
        return lock(File.OPER_READ, null, (byte)0, bufOut, start, count);
    }
    
    /**
     *  Lee a lo más (bufOut.length - start) bytes, y los almacena en bufOut.
     *
     * @param bufOut Arreglo de bytes, es el destino de los datos leídos.
     * @param start Posición del primer byte leído. Este valor debe estar
     *              entre 0 y bufOut.length.
     *
     * @return Devuelve el número de bytes leídos. Este valor puede ser
     *         menor que (bufOut.length - start). Por ejemplo, si no hay
     *         más bytes en el archivo o si no hay más bytes disponibles
     *         inmediatamente. Si se llega al final del archivo devuelve
     *         0 y eof() devuelve true. En caso de error devuelve File.ERR
     *         y err() devuelve true.
     */
    public int read(byte[] bufOut, int start) {
        return read(bufOut, start, bufOut.length - start);
    }
    
    /**
     *  Lee a lo más bufOut.length bytes, y los almacena en bufOut.
     *
     * @param bufOut Arreglo de bytes, es el destino de los datos leídos.
     * @param start Posición del primer byte leído. Este valor debe estar
     *              entre 0 y bufOut.length.
     *
     * @return Devuelve el número de bytes leídos. Este valor puede ser
     *         menor que bufOut.length. Por ejemplo, si no hay más en el
     *         en el archivo o si no hay más bytes disponibles
     *         inmediatamente. Si se llega al final del archivo devuelve
     *         0 y eof() devuelve true. En caso de error devuelve File.ERR
     *         y err() devuelve true.
     */
    public int read(byte[] bufOut) {
        return read(bufOut, 0);
    }
    
    /**
     *  Borra un archivo
     *
     * @param String filename   ruta y nombre del archivo
     */
    public static final void delete(final String filename){
        try {
            RecordStore.deleteRecordStore(filename);
        } catch (RecordStoreNotFoundException rsnfx) {
        } catch (RecordStoreException rsx) {
        }
    }
    
    /**
     *  Cambia el nombte de un archivo
     *
     * @param String filename       nombre original del archivo
     * @param String newfileName    nuevo nombre del archivo.
     */
    public static final void rename(final String filename, final String newfileName){
        // En un futuro... por ahora no es necesario.
        // Don't worry be hacker!!!.
    }
}
